#!/usr/bin/perl
#
# Mainenance script for database files
#
# Everytime I find something I occasionally need to do, this should get
# another option
#
use POSIX;
#use FileTable;
use Counter;
use FileTableDebug;
use Validate::Persons;
use Validate::Machines;
#use Validate::Friends;
#use Validate::Usergroups;

$flags = O_RDWR;
$db = "db"; # Default location for databases

while ($ARGV[0] ne "") {
    $cmd = shift;
    if ($cmd eq "clean") {
	$filename = shift;
	$file = openfile($filename);
	$file -> reclean;
    } elsif ($cmd eq "noempty") {
	$filename = shift;
	$file = openfile($filename);
	$count = $file -> noempty;
	print "$count empty records removed\n";
    } elsif ($cmd eq "cleancheck") {
	$filename = shift;
	$file = openfile($filename);
	$count = $file -> cleancheck;
	print "$count errors\n";
    } elsif ($cmd eq "index") {
	$filename = shift;
	$file = openfile($filename);
	$file -> reindex;
    } elsif ($cmd eq "cleansome") {
	$filename = shift;
	$file = openfile($filename);
	while (<>) {
	    if (/^(\d+):/) {
		$rec = $file->get($1);
		$rec -> clean();
	    }
	}
    } elsif ($cmd eq "cleanempty") {
	# to get around a worrying breakdown in the
	# "clean" bases, where lots of recs exist-but-empty
	$filename = shift;
	$fieldname = shift;
	$count = 100;
	$count = shift if $ARGV[0] =~ /^\d+$/;
	$file = openfile($filename);
	while ($key = $file->each) {
	    $cl = $file->getclean($key);
	    if (!$cl->{$fieldname}) {
		$rec = $file->get($key);
		print STDERR "Cleaning $key ($fieldclean/$noclean/$ok)\n";
		$rec->clean;
		++ $fieldclean;
	    } else {
		++ $ok;
            }
	    last if $noclean + $fieldclean >= $count;
	}
	print "$ok OK recs, $fieldclean empty-field cleans\n";
    } elsif ($cmd eq "rebuild") {
	$filename = shift;
	$checkrec = shift; # the disappearing record....
	$file = openfile($filename);
	print STDERR "Rebuilding DB files for $filename\n";
	if (defined($checkrec)) {
	    $file -> rebuilddb(\&checkrecord);
	} else {
	    $file -> rebuilddb();
	}
    } elsif ($cmd eq "checkindex") {
	$filename = shift;
	$file = openfile($filename);
	$field = shift;
	$value = shift;
	if ($file->checkindex($field, $value, $flags)) {
	    print "Error\n";
	} else {
	    print "No error\n";
	}
    } elsif ($cmd eq "checkallindex") {
	# Now this is a gruesome hack....
	$filename = shift;
	print STDERR "Deleting errors\n" if $flags & O_RDWR;
	$file = openfile($filename);
	$count = 0; $errcount = 0;
        while (($pair, $rec) = each %{$file->{"*INDEXFILE"}}) {
	    ($field, $value) = split(":", $pair);
	    ++ $count;
	    print STDERR "$errcount/$count ($value)\r" if $count % 100 == 0
		&& -t STDERR;
	    if ($file->checkindex($field, $value, $flags)) {
		print "$value -> $rec: Error\n";
		++ $errcount;
	    }
	}
	print STDERR "\n$count entries, $errcount errors\n";
    } elsif ($cmd eq "stats") {
	$filename = shift;
	$file = openfile($filename);
	$keys = $file->keys();
	print "Number of keys by keys(): ";
	print $keys, "\n";
	$reccount = 0;
	while ($key = $file->each()) {
	    #$rec = $file->get($key);
	    #if (!$rec) {
	#	print STDERR "Error: $key in each() but does not have record\n";
	#    } else {
	#	$maxlen = $rec->recsize if $rec->recsize > $maxlen;
	#    }
	    ++ $reccount;
	}
	print "Number of keys by each(): $reccount\n";
	print "Largest record: $maxlen\n";
    } elsif ($cmd eq "rebuildone") {
	$filename = shift;
	$key = shift;
	$file = openfile($filename);
	$file -> rebuildrec($key);
    } elsif ($cmd eq "dump") {
	$filename = shift;
	$key = shift;
	$file = openfile($filename);
	while ($key = $file->each()) {
	    $rec = $file->get($key);
	    if (!$rec) {
		print STDERR "Error: No rec for $key\n";
		next;
	    }
	    print "//", $rec->key, "\n";
	    for $field (keys(%$rec)) {
		if ($field =~ /^([-a-z0-9A-Z]+)$/) {
		    print "$field:$rec->{$field}\n";
		}
	    }
	}
    } elsif ($cmd eq "select") {
	$filename = shift;
	$expression = shift;
	$file = openfile($filename);
	@recs = $file -> select($expression);
	for $rec (@recs) {
	    print "//", $rec->key, "\n";
	    for $field (keys(%$rec)) {
		if ($field =~ /^([-a-z0-9A-Z]+)$/) {
		    print "$field:$rec->{$field}\n";
		}
	    }
	}	    
    } elsif ($cmd eq "count") {
	$filename = shift;
	$expression = shift;
	$file = openfile($filename);
	$count = $file -> count($expression);
	print "$count\n";
    } elsif ($cmd eq "selectany") {
	$filename = shift;
	$expression = shift;
	$file = openfile($filename);
	@recs = $file -> selectany($expression);
	for $rec (@recs) {
	    print "//----\n";
	    for $field (keys(%$rec)) {
		print "$field:$rec->{$field}\n";
	    }
	}	    
    } elsif ($cmd eq "addfield") {
	$filename = shift;
	$field = shift;
	$datatype = shift;
	$file = openfile($filename);
	$modcount = $file->addsqlfield($field, $datatype);
	print "$modcount records modified\n";
    } elsif ($cmd eq "renamefield") {
	$filename = shift;
	$field = shift;
	my $newname = shift;
	$file = openfile($filename);
	my $modcount = $file->renamefield($field, $newname);
	print "$modcount records modified\n";
# OPTIONS.....	
    } elsif ($cmd eq "-c") {
	$flags |= O_CREAT;
	print STDERR "Flags are set to $flags\n";
    } elsif ($cmd eq "-r") {
	$flags &= ~O_RDWR; # Clear read/write flag
    } elsif ($cmd eq "-t") {
	$db = "test-db";
    } else {
	print "No such command as $cmd\n";
    }
}



sub openfile {
    my $filename = shift;
    my $file;

    if (! -f $filename.conf && -f "$db/$filename.conf") {
	$filename = "$db/$filename";
    }
    $file = FileTable::open($filename, $flags);
    $file;
}

sub tickfast {
    print STDERR join(" ", @_), "\n";
}

sub checkrecord {
# Heavy-duty rebuild consistency checker:
# Every time a new record is done, check if rec exists.
# If key is named "-keys", check number of keys by keys() instead.
# Break if it disappears.
# Blah.
    $tickno = shift;
    $lineno = shift;
    if ($checkrec) {
	if ($checkrec eq "-keys") {
	    my $keys;
	    $keys = $file->keys();
	    if ($keys < $prevkeys) {
		print STDERR "Nkeys down from $prevkeys to $keys\n";
		print STDERR "Operation $tickno line $lineno\n";
		die "No. of keys reduced\n";
	    }
	} elsif ($checkrec eq "-crash") {
	    # checking each access is just Too Slow
	    if ($tickno % 100 == 0) {
		my $count = eachcrash();
		print STDERR "op $tickno line $lineno count $count", "\n";
	    }
	    return;
	} else {
	    $rec = $file->get($checkrec);
	    if (!$seenrec && $rec) {
		print STDERR "$checkrec now exists\n";
		$seenrec = 1;
	    } elsif ($seenrec && !$rec) {
		print STDERR "$checkrec is gone - aborting\n";
		print STDERR "Operation $tickno line $lineno\n";
		die "Watched record gone\n";
	    }
	}
    }
    print STDERR "op $tickno line $lineno", "\r" if $tickno % 100 == 0;
}

sub eachcrash {
# Attempt to crash the program by running each() with array access
# on the file. Hacky!
    my $key;
    my $val;
    my $reccount;
    while ($key = $file->each()) {
	$val = $file->{"*FILE"}->{$key}; # finger of death
	++ $reccount;
    }
    return $reccount;
}
    


